AWS Systems Manager ランコマンドの出力を AWS CLI で直接確認するためにはひと工夫が必要
ランコマンドの結果を AWS CLI で取得したい
コンバンハ、千葉(幸)です。
AWS Systems Manager ランコマンドを用いると複数のインスタンスを対象にコマンドを実行できます。
例えば以下の画像では 20 台のインスタンスに対して同一の処理を一括で行なっています。具体的にはインスタンスにインストール済みの CloudWatch エージェントのバージョンを確認する、というコマンドを実行しています。
これらの実行結果をコンソールから確認したいとなった場合、「コマンドの実行結果画面からターゲットのインスタンスを選択」→「インスタンスの詳細画面で出力を確認」という操作をターゲットごとに行う必要があります。
台数が多くなければそこまで手間ではありませんが、せっかくなら AWS CLI でササっと確認したいという思いを抱きました。検討を進めていく中で少し詰まった部分があるのでご紹介します。
まとめ
- AWS CLI でランコマンドの結果を確認できるコマンドは以下がある
- 両者ともに出力を確認できるのは
Plugin
によるものである1.list-
コマンドで Plugin の詳細を表示するためには--details
オプションの付与が必要2.get-
コマンドではデフォルトで Plugin が表示されるが、1.list-
コマンドに比べ対応しているドキュメントが少ない?1.list-
コマンドの Plugin のOutput
では標準出力と標準エラー出力が区別されない
2.get-
コマンドではコマンド ID に加えてインスタンス ID の指定が必須- 両者ともに「S3 バケットへの書き込み」を有効化していた場合、標準出力、標準エラー出力の S3 オブジェクト URL が記録される
- パス形式の URL なのでちょっと取り回しがしづらい
もろもろ鑑みて、1.list-
コマンドの方が使いやすいと感じました。その上で、--details
オプションを付与しないと詳細が表示されない、というのが躓きポイントでした。
使用した AWS CLI のバージョン
今回のエントリの内容は以下バージョンの AWS CLI で試行した内容に基づいています。
% aws --version aws-cli/2.9.1 Python/3.9.11 Darwin/22.1.0 exe/x86_64 prompt/off
AWS SSM ランコマンドの実行例
AWS CLI コマンドの詳細を見ていく前に、例としてランコマンドを実行しておきます。
Systems Manager コンソールから「Run Command(左ペイン)」→「Run Command(ボタン)」と進みます。
今回はドキュメントとしてAWS-RunShellScript
を使用します。ターゲットとなるインスタンスで実行させる内容を、パラメータCommands
に指定します。
ここでは以下のようにメタデータから自身のプライベート IP アドレスを取得する処理を指定しました。
#!/bin/bash curl http://169.254.169.254/latest/meta-data/local-ipv4
ターゲットを指定します。今回は環境に 3 台のマネージドノードが存在するため、それぞれ手動で選択します。なお、すべて Linux のインスタンスです。
出力オプションとして S3 バケットへの書き込みを有効化します。このオプションを使用する場合、ターゲットインスタンスが対象の S3 バケットに書き込み権限を持っている必要があることに注意してください。
その他のオプションはデフォルトのまま画面下部に進んでいくと、今回指定した内容を AWS CLI で実行する際のコマンドを記述してくれています。
テキストにするとこんな感じ。似たようなコマンドを実行する際の参考にできそうです。
aws ssm send-command --document-name "AWS-RunShellScript" --document-version "1" --targets '[{"Key":"InstanceIds","Values":["i-02148b21728321265","i-04f6c32b1d9c46916","i-0b14c40e0479d0009"]}]' --parameters '{"workingDirectory":[""],"executionTimeout":["3600"],"commands":["#!/bin/bash","curl http://169.254.169.254/latest/meta-data/local-ipv4"]}' --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --output-s3-bucket-name "chiba-ssm-log" --output-s3-key-prefix "hogehoge" --region ap-northeast-1
コンソール上でコマンド実行を行います。以下のように実行結果が表示されました。
ターゲットの詳細を確認してみるとこのような感じ。Output(標準出力)には意図どおりプライベート IP アドレスが記述されています。
↑あまり意識していなかったのですが、Error(標準エラー出力)には進捗メーターが表示されています。このエラー出力を抑止したいという場合は以下のような工夫が必要です。
# curlの-sオプションでエラー出力を抑止 curl -s http://169.254.169.254/latest/meta-data/local-ipv4 # 標準エラー出力を/dev/nullに捨てる curl http://169.254.169.254/latest/meta-data/local-ipv4 2> /dev/null
aws ssm list-command-invocations
上記のコマンド実行結果をaws ssm list-command-invocations
で確認してみます。
以下のように結果が得られます。ここでCommandPlugins
の内容が空になっており、「もしかして都度 S3 からダウンロードして確認しないといけない?」と試行錯誤をしていました。
% COMMAND_ID=5431f2a8-1fc2-47b5-aca0-3abc55cac49d % aws ssm list-command-invocations --command-id $COMMAND_ID { "CommandInvocations": [ { "CommandId": "2ca57e61-f070-4803-b008-3050f8c15a5c", "InstanceId": "i-0b14c40e0479d0009", "InstanceName": "ip-172-31-38-78.ap-northeast-1.compute.internal", "Comment": "", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "1", "RequestedDateTime": "2022-12-28T03:18:14.983000+09:00", "Status": "Success", "StatusDetails": "Success", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stderr", "CommandPlugins": [], "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }, { ...2台目の実行結果... }, { ...3台目の実行結果... } ] }
--details オプションを付与する
リファレンスを見ると以下の記述があります。アウトプットの詳細を見るためには--details
オプションの明示的な指定が必要なようです。
--details
|--no-details
(boolean)(Optional) If set this returns the response of the command executions and any command output. The default value is
false
.
オプションを指定して実行した場合の結果が以下の通り。CommandPlugins
の詳細が記録されており、Output
として出力が記録されています。
% COMMAND_ID=2ca57e61-f070-4803-b008-3050f8c15a5c % aws ssm list-command-invocations --command-id $COMMAND_ID\ --details { "CommandInvocations": [ { "CommandId": "2ca57e61-f070-4803-b008-3050f8c15a5c", "InstanceId": "i-0b14c40e0479d0009", "InstanceName": "ip-172-31-38-78.ap-northeast-1.compute.internal", "Comment": "", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "1", "RequestedDateTime": "2022-12-28T03:18:14.983000+09:00", "Status": "Success", "StatusDetails": "Success", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stderr", "CommandPlugins": [ { "Name": "aws:runShellScript", "Status": "Success", "StatusDetails": "Success", "ResponseCode": 0, "ResponseStartDateTime": "2022-12-28T03:18:15.183000+09:00", "ResponseFinishDateTime": "2022-12-28T03:18:15.499000+09:00", "Output": "172.31.38.78\n----------ERROR-------\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 12 100 12 0 0 8510 0 --:--:-- --:--:-- --:--:-- 12000\n", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stderr", "OutputS3Region": "ap-northeast-1", "OutputS3BucketName": "chiba-ssm-log", "OutputS3KeyPrefix": "hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript" } ], "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }, { ...2台目の実行結果... }, { ...3台目の実行結果... } ] }
↑Output
では「標準出力」と「標準エラー出力」が分かれておらず、\n----------ERROR-------\n
で両者をつないで出力するようです。
標準出力の結果だけ欲しい、という場合は先ほど見たように標準エラー出力を抑止するようにするとよさそうです。
標準出力、標準エラー出力の S3 オブジェクトの URL
上記の実行結果には標準出力、標準エラー出力が記録された S3 オブジェクトの URL が記述されています。
StandardOutputUrl
を例にとると以下の URL です。
https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout
これは以下の構成のようです。
https://s3.{リージョン}.amazonaws.com/{バケット名}/{プレフィックス}/{コマンドID}/{インスタンスID}/{ランタイムコンフィグ?}/{ランタイムID?}/stdout
ランタイムうんぬんと言っているのはドキュメントAWS-RunShellScript
のコンテンツの以下の部分です。
{ "schemaVersion": "1.2", "description": "Run a shell script or specify the commands to run.", "parameters": { "commands": { "type": "StringList", "description": "(Required) Specify a shell script or a command to run.", "minItems": 1, "displayType": "textarea" }, "workingDirectory": { "type": "String", "default": "", "description": "(Optional) The path to the working directory on your instance.", "maxChars": 4096 }, "executionTimeout": { "type": "String", "default": "3600", "description": "(Optional) The time in seconds for a command to complete before it is considered to have failed. Default is 3600 (1 hour). Maximum is 172800 (48 hours).", "allowedPattern": "([1-9][0-9]{0,4})|(1[0-6][0-9]{4})|(17[0-1][0-9]{3})|(172[0-7][0-9]{2})|(172800)" } }, "runtimeConfig": { "aws:runShellScript": { "properties": [ { "id": "0.aws:runShellScript", "runCommand": "{{ commands }}", "workingDirectory": "{{ workingDirectory }}", "timeoutSeconds": "{{ executionTimeout }}" } ] } } }
一度は非推奨化とアナウンスのあったパス形式の URL が使われているのがなんとも言えないところです。
S3 URL 形式に変換して aws s3 cp でストリーミングダウンロードする、というのは以下コマンドで実現できました。
% URL=https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout % echo $URL | sed -e 's/s3.*.com\///' -e 's/https/s3/' | read s3uri; aws s3 cp $s3uri - 172.31.38.78%
完全な余談ですが、仮に今後この URL が仮想ホスト形式で返って来るようになった場合には以下のような形で変換することになるかと思います。
% URL2=https://chiba-ssm-log.s3.ap-northeast-1.amazonaws.com/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout % echo $URL2 | sed -e 's/.s3.*.com//' -e 's/https/s3/' s3://chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout
aws ssm get-command-invocation
同じランコマンドの結果を対象にaws ssm get-command-invocation
コマンドを実行してみます。
aws ssm get-command-invocation
ではaws ssm list-command-invocations
と異なりインスタンス ID の指定が必須です。
% COMMAND_ID=2ca57e61-f070-4803-b008-3050f8c15a5c % INSTANCE_ID=i-0b14c40e0479d0009 % aws ssm get-command-invocation --command-id $COMMAND_ID --instance-id $INSTANCE_ID { "CommandId": "2ca57e61-f070-4803-b008-3050f8c15a5c", "InstanceId": "i-0b14c40e0479d0009", "Comment": "", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "1", "PluginName": "aws:runShellScript", "ResponseCode": 0, "ExecutionStartDateTime": "2022-12-27T18:18:15.183Z", "ExecutionElapsedTime": "PT0.316S", "ExecutionEndDateTime": "2022-12-27T18:18:15.183Z", "Status": "Success", "StatusDetails": "Success", "StandardOutputContent": "172.31.38.78", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorContent": " % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 12 100 12 0 0 8510 0 --:--:-- --:--:-- --:--:-- 12000\n", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stderr", "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }
大まかにはaws ssm list-command-invocations
と同じであるものの、細部で項目の差異があります。また、標準出力と標準エラー出力が項目として分かれているのが大きな特徴かと思います。
さらに詳細な情報を出力する、というオプションはないようでした。インスタンス ID の指定が必要なところが取り回しがしづらい部分かなという印象です。
CommandPlugins は特定のドキュメント限定?
aws ssm list-command-invocations
では--details
オプションを付与することでCommandPlugins
の中身を出力することができました。
CommandPlugins
について、リファレンスに気になる記述があります。
Name -> (string)
The name of the plugin. Must be one of the following:
aws:updateAgent
,aws:domainjoin
,aws:applications
,aws:runPowerShellScript
,aws:psmodule
,aws:cloudWatch
,aws:runShellScript
, oraws:updateSSMAgent
.
特定の名称のプラグインしかここには含まれない、と書いてありそうです。また、このプラグインはランコマンドドキュメント名とある程度相関性がありそうです。
上記のプラグイン名に関連しなさそうなAWS-ConfigureAWSPackage
ドキュメントを実行してその結果を確認してみることにします。
以下を指定し、実行します。
- コマンドのパラメータ:
Name
にAmazonCloudWatchAgent
- ターゲット:インスタンス 2 台
- S3 バケットへの書き込み:有効化(
chiba-ssm-log/cwagent
)
aws ssm list-command-invocations の --details なし
--details
なしだとCommandPlugins
の中身が出力されないのは想定通りです。
% COMMAND_ID=1b85a43b-65f8-4f98-94e9-0489c9092a69 % aws ssm list-command-invocations --command-id $COMMAND_ID { "CommandInvocations": [ { "CommandId": "1b85a43b-65f8-4f98-94e9-0489c9092a69", "InstanceId": "i-0a9960e6e67d81a58", "InstanceName": "ip-172-31-32-131.ap-northeast-1.compute.internal", "Comment": "", "DocumentName": "AWS-ConfigureAWSPackage", "DocumentVersion": "1", "RequestedDateTime": "2022-12-28T04:37:46.562000+09:00", "Status": "Success", "StatusDetails": "Success", "StandardOutputUrl": "", "StandardErrorUrl": "", "CommandPlugins": [], "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }, { ...略... } ] }
気になるのは S3 バケットへの書き込みを有効化しているのにStandardOutputUrl
やStandardErrorUrl
がブランクである点です。
aws ssm list-command-invocations の --details あり
--details
オプションを付与することでCommandPlugins
の中身が出力されます。ドキュメントのステップごとにプラグインが分かれているようです。
% COMMAND_ID=1b85a43b-65f8-4f98-94e9-0489c9092a69 % aws ssm list-command-invocations --command-id $COMMAND_ID\ --details { "CommandInvocations": [ { "CommandId": "1b85a43b-65f8-4f98-94e9-0489c9092a69", "InstanceId": "i-0a9960e6e67d81a58", "InstanceName": "ip-172-31-32-131.ap-northeast-1.compute.internal", "Comment": "", "DocumentName": "AWS-ConfigureAWSPackage", "DocumentVersion": "1", "RequestedDateTime": "2022-12-28T04:37:46.562000+09:00", "Status": "Success", "StatusDetails": "Success", "StandardOutputUrl": "", "StandardErrorUrl": "", "CommandPlugins": [ { "Name": "configurePackage", "Status": "Success", "StatusDetails": "Success", "ResponseCode": 0, "ResponseStartDateTime": "2022-12-28T04:37:46.703000+09:00", "ResponseFinishDateTime": "2022-12-28T04:37:54.796000+09:00", "Output": "Initiating arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275 install\nPlugin aws:runShellScript ResultStatus Success\ninstall output: Running sh install.sh\ncreate group cwagent, result: 0\ncreate user cwagent, result: 0\n\nSuccessfully installed arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275\n", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsconfigurePackage/configurePackage/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsconfigurePackage/configurePackage/stderr", "OutputS3Region": "ap-northeast-1", "OutputS3BucketName": "chiba-ssm-log", "OutputS3KeyPrefix": "cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsconfigurePackage" }, { "Name": "createDownloadFolder", "Status": "Success", "StatusDetails": "Success", "ResponseCode": 0, "ResponseStartDateTime": "2022-12-28T04:37:46.703000+09:00", "ResponseFinishDateTime": "2022-12-28T04:37:46.703000+09:00", "Output": "Step execution skipped due to unsatisfied preconditions: '\"StringEquals\": [platformType, Windows]'. Step name: createDownloadFolder", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsrunPowerShellScript/0.createDownloadFolder/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsrunPowerShellScript/0.createDownloadFolder/stderr", "OutputS3Region": "ap-northeast-1", "OutputS3BucketName": "chiba-ssm-log", "OutputS3KeyPrefix": "cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsrunPowerShellScript" } ], "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }, { ...略... } ] }
それぞれのプラグインごとに出力や出力先 URL が用意されています。
プラグインの名称は以下に該当しないものなので、このメッセージは何を意味するのか図りかねています。
Name -> (string)
The name of the plugin. Must be one of the following:
aws:updateAgent
,aws:domainjoin
,aws:applications
,aws:runPowerShellScript
,aws:psmodule
,aws:cloudWatch
,aws:runShellScript
, oraws:updateSSMAgent
.
aws ssm get-command-invocation
aws ssm get-command-invocation
で確認すると、AWS-RunShellScript
ドキュメントを実行したときの結果とは異なり、各種アウトプットに関する情報が出力されていません。
コマンドの出力そのものはもちろん、それらの S3 オブジェクトの URL も含まれていないようです。
% COMMAND_ID=1b85a43b-65f8-4f98-94e9-0489c9092a69 % INSTANCE_ID=i-0a9960e6e67d81a58 % aws ssm get-command-invocation --command-id $COMMAND_ID --instance-id $INSTANCE_ID { "CommandId": "1b85a43b-65f8-4f98-94e9-0489c9092a69", "InstanceId": "i-0a9960e6e67d81a58", "Comment": "", "DocumentName": "AWS-ConfigureAWSPackage", "DocumentVersion": "1", "ResponseCode": 0, "ExecutionEndDateTime": "", "Status": "Success", "StatusDetails": "Success", "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }
このあたりの線引きについて詳細は詰めきれなかったのですが、「使用するドキュメントによってはaws ssm get-command-invocation
で出力結果が得られない」と覚えておくとよさそうです。
終わりに
AWS Systems Manager ランコマンドの実行結果を AWS CLI で取得したい、という内容でした。
冒頭のまとめの繰り返しになりますが、以下のあたりを意識しておくとよさそうです。
- AWS CLI でランコマンドの結果を確認できるコマンドは以下がある
- 両者ともに出力を確認できるのは
Plugin
によるものである1.list-
コマンドで Pluginの詳細を表示するためには--details
オプションの付与が必要2.get-
コマンドではデフォルトで Plugin が表示されるが、1.list-
コマンドに比べ対応しているドキュメントが少ない?1.list-
コマンドの Plugin のOutput
では標準出力と標準エラー出力が区別されない
2.get-
コマンドではコマンド ID に加えてインスタンス ID の指定が必須
実行結果を一覧でサッと出力するのであればaws ssm list-command-invocations
を軸に考えるのがよさそうです。具体的にどうするんだ、というのはまた別の話にしたいと思います。
以上、 チバユキ (@batchicchi) がお送りしました。
追記
一括で取得する際のコマンド例を以下にまとめました。